﻿//和信号有关的函数放这里
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>

#include "ngx_macro.h"
#include "ngx_global.h"
#include "ngx_func.h" 

//一个信号有关的结构 ngx_signal_t
typedef struct 
{
    int           signo;       //信号对应的数字编号 ，每个信号都有对应的#define ，大家已经学过了 
    const  char   *signame;    //信号对应的中文名字 ，比如SIGHUP 

    //信号处理函数,这个函数由我们自己来提供，但是它的参数和返回值是固定的【操作系统就这样要求】,大家写的时候就先这么写，也不用思考这么多；
    void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext); //函数指针,   siginfo_t:系统定义的结构
} ngx_signal_t;

//声明一个信号处理函数
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext); //static表示该函数只在当前文件内可见
static void ngx_process_get_status(void);                                      //获取子进程的结束状态，防止单独kill子进程时子进程变成僵尸进程

//数组 ，定义本系统处理的各种信号，我们取一小部分nginx中的信号，并没有全部搬移到这里，日后若有需要根据具体情况再增加
//在实际商业代码中，你能想到的要处理的信号，都弄进来
ngx_signal_t  signals[] = {
    // signo      signame             handler
    { SIGHUP,    "SIGHUP",           ngx_signal_handler },        //终端断开信号，对于守护进程常用于reload重载配置文件通知--标识1
    { SIGINT,    "SIGINT",           ngx_signal_handler },        //标识2   
	{ SIGTERM,   "SIGTERM",          ngx_signal_handler },        //标识15
    { SIGCHLD,   "SIGCHLD",          ngx_signal_handler },        //子进程退出时，父进程会收到这个信号--标识17
    { SIGQUIT,   "SIGQUIT",          ngx_signal_handler },        //标识3
    { SIGIO,     "SIGIO",            ngx_signal_handler },        //指示一个异步I/O事件【通用异步I/O信号】
    { SIGSYS,    "SIGSYS, SIG_IGN",  NULL               },        //我们想忽略这个信号，SIGSYS表示收到了一个无效系统调用，如果我们不忽略，进程会被操作系统杀死，--标识31
                                                                  //所以我们把handler设置为NULL，代表 我要求忽略这个信号，请求操作系统不要执行缺省的该信号处理动作（杀掉我）
    //...日后根据需要再继续增加
    { 0,         NULL,               NULL               }         //信号对应的数字至少是1，所以可以用0作为一个特殊标记
};

//初始化信号的函数，用于注册信号处理程序
//返回值：0成功  ，-1失败
int ngx_init_signals()
{
    ngx_signal_t      *sig;  //指向自定义结构数组的指针 
    struct sigaction   sa;   //sigaction：系统定义的跟信号有关的一个结构，我们后续调用系统的sigaction()函数时要用到这个同名的结构

    for (sig = signals; sig->signo != 0; sig++)  //将signo ==0作为一个标记，因为信号的编号都不为0；
    {        
        //我们注意，现在要把一堆信息往 变量sa对应的结构里弄 ......
        memset(&sa,0,sizeof(struct sigaction));

        if (sig->handler)  //如果信号处理函数不为空，这当然表示我要定义自己的信号处理函数
        {
            sa.sa_sigaction = sig->handler;  //sa_sigaction：指定信号处理程序(函数)，注意sa_sigaction也是函数指针，是这个系统定义的结构sigaction中的一个成员（函数指针成员）；
            sa.sa_flags = SA_SIGINFO;        //sa_flags：int型，指定信号的一些选项，设置了该标记(SA_SIGINFO)，就表示信号附带的参数可以被传递到信号处理函数中
                                                //说白了就是你要想让sa.sa_sigaction指定的信号处理程序(函数)生效，你就把sa_flags设定为SA_SIGINFO
        }
        else
        {
            sa.sa_handler = SIG_IGN; //sa_handler:这个标记SIG_IGN给到sa_handler成员，表示忽略信号的处理程序，否则操作系统的缺省信号处理程序很可能把这个进程杀掉；
                                      //其实sa_handler和sa_sigaction都是一个函数指针用来表示信号处理程序。只不过这两个函数指针他们参数不一样， sa_sigaction带的参数多，信息量大，
                                       //而sa_handler带的参数少，信息量少；如果你想用sa_sigaction，那么你就需要把sa_flags设置为SA_SIGINFO；                                       
        } //end if

        sigemptyset(&sa.sa_mask);   //比如咱们处理某个信号比如SIGUSR1信号时不希望收到SIGUSR2信号，那咱们就可以用诸如sigaddset(&sa.sa_mask,SIGUSR2);这样的语句针对信号为SIGUSR1时做处理，这个sigaddset三章五节讲过；
                                    //这里.sa_mask是个信号集（描述信号的集合），用于表示要阻塞的信号，sigemptyset()这个函数咱们在第三章第五节讲过：把信号集中的所有信号清0，本意就是不准备阻塞任何信号；
                                    
        
        //设置信号处理动作(信号处理函数)，说白了这里就是让这个信号来了后调用我的处理程序，有个老的同类函数叫signal，不过signal这个函数被认为是不可靠信号语义，不建议使用，大家统一用sigaction
        if (sigaction(sig->signo, &sa, NULL) == -1) //参数1：要操作的信号
                                                     //参数2：主要就是那个信号处理函数以及执行信号处理函数时候要屏蔽的信号等等内容
                                                      //参数3：返回以往的对信号的处理方式【跟sigprocmask()函数边的第三个参数是的】，跟参数2同一个类型，我们这里不需要这个东西，所以直接设置为NULL；
        {   
            ngx_log_error_core(NGX_LOG_EMERG,errno, "sigaction(%s) failed", sig->signame); //显示到日志文件中去的 
            return -1; //有失败就直接返回
        }	
        else
        {            
            //ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) succed!",sig->signame);     //成功不用写日志 
            // ngx_log_stderr(0, "sigaction(%s) succed!", sig->signame); //直接往屏幕上打印看看 ，不需要时可以去掉
        }
    } //end for
    return 0; //成功    
}

//信号处理函数
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{
    ngx_signal_t *sig;
    char *action;
    for (sig = signals; sig->signo != 0; ++sig)
    {
        if (sig->signo == signo)
        {// 将sig指向这个信号的信息
            break;
        }
    }

    action = (char *)"";  // 目前还没有什么动作；

    if (ngx_process == NGX_PROCESS_MASTER)
    {// 如果当前进程是MASTERh进程
        switch (signo)
        {
        case SIGCHLD:
            // 表示某个子进程死了
            ngx_reap = 1; // 子进程状态变化标记为有变化
            break;
        
        default:
            break;
        }
    }
    else if (ngx_process == NGX_PROCESS_WORKER)
    {// 如果当前进程是WORKER进程

    }
    else
    {// 其他类型暂未定义
        
    }
    
    if (siginfo && siginfo->si_pid) // siginfo->si_pid: 代表发送该信号的进程id
    {
        ngx_log_error_core(NGX_LOG_NOTICE, 0, "signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, action); 
    }
    else
    {// 没有发送该信号的进程id，所以不显示发送该信号的进程id
        ngx_log_error_core(NGX_LOG_NOTICE, 0, "signal %d (%s) received %s", signo, sig->signame, action);
    }
    if (signo == SIGCHLD)
    {// 子进程状态有变化，通常是意外退出
        ngx_process_get_status();
    }
    return;
}

static void ngx_process_get_status(void)
{
    pid_t        pid;     // 接收waitpid返回的值
    int          status;  // 记录子进程是怎么死的，在waitpid中作为传出参数
    int          err;
    int          one = 0; // one时标记信号被正常处理过一次

    for ( ; ; )
    {
        pid = waitpid(-1, &status, WNOHANG); // WNOHANG代表此函数的阻塞直接返回
        if (0 == pid)
        {// 代表子进程还没有结束
            return;
        }

        if (-1  == pid)
        {// 表示waitpid调用有错误
            err = errno;
            if (err == EINTR)
            {// 调用某个信号中断了waitpid导致了错误
                continue;
            }

            if (err == ECHILD && one)
            {// 没有子进程，而且被处理过了一次，直接return
                return;
            }

            if (err == ECHILD)
            {// 没有子进程，没有被处理过，打印一次日志再return
                ngx_log_error_core(NGX_LOG_INFO, err, "waitpid() failed!");
                return;
            }
            ngx_log_error_core(NGX_LOG_ALERT, err, "waitpid() failed!");
            return;
        } // end if (-1  == pid)

        // 执行到此代表waitpid函数成功返回，在这里打印一些日志来记录子进程的退出
        one = 1; // 标记waitpid返回了正常的值
        if (WTERMSIG(status))
        {// 获取让子进程终止的信号编号
            ngx_log_error_core(NGX_LOG_ALERT, 0, "pid = %P exited on signal %d!", pid, WTERMSIG(status));
        }
        else
        {// WEXITSTATUS()获取子进程传递给exit或者_exit参数的低八位
            ngx_log_error_core(NGX_LOG_NOTICE, 0, "pid = %P exited with code %d!", pid, WEXITSTATUS(status));
        } // end if (WTERMSIG(status))
    } // end for ( ; ; )
    return;
}
